# DerbyParameterBinders.java

    package org.codefilarete.stalactite.sql.derby.statement.binder;

    import java.io.IOException;
    import java.io.InputStream;
    import java.sql.Blob;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Timestamp;
    import java.time.LocalDateTime;
    import java.time.LocalTime;
    import java.util.Arrays;

    import org.codefilarete.stalactite.sql.statement.binder.DefaultParameterBinders;
    import org.codefilarete.stalactite.sql.statement.binder.InMemoryBlobSupport;
    import org.codefilarete.stalactite.sql.statement.binder.LambdaParameterBinder;
    import org.codefilarete.stalactite.sql.statement.binder.LocalTimeBinder;
    import org.codefilarete.stalactite.sql.statement.binder.NullAwareParameterBinder;
    import org.codefilarete.stalactite.sql.statement.binder.NullAwarePreparedStatementWriter;
    import org.codefilarete.stalactite.sql.statement.binder.NullAwareResultSetReader;
    import org.codefilarete.stalactite.sql.statement.binder.ParameterBinder;
    import org.codefilarete.stalactite.sql.statement.binder.ResultSetReader.LambdaResultSetReader;
    import org.codefilarete.tool.Nullable;
    import org.codefilarete.tool.io.IOs;

    import static org.codefilarete.stalactite.sql.statement.binder.DefaultPreparedStatementWriters.BINARYSTREAM_WRITER;
    import static org.codefilarete.stalactite.sql.statement.binder.DefaultPreparedStatementWriters.BLOB_WRITER;
    import static org.codefilarete.stalactite.sql.statement.binder.DefaultPreparedStatementWriters.BYTES_WRITER;

    /**
     * @author Guillaume Mary
     */
    public final class DerbyParameterBinders {

    	/**
    	 * Equivalent to {@link DefaultParameterBinders#BINARYSTREAM_BINDER} but makes a copy of the read binary stream from ResultSet, else later reading fails
    	 * with "java.io.IOException: The object is already closed",
    	 * even it the connection is still open and the ResultSet hasn't changed of row. Looks weird.
    	 * Looks like https://issues.apache.org/jira/browse/DERBY-6341 but I don't get where I'm wrong (don't see where I read twice the ResultSet).
    	 */
    	public static final ParameterBinder<InputStream> BINARYSTREAM_BINDER = new LambdaParameterBinder<>(new LambdaResultSetReader<>((ResultSet resultSet, String columnName) ->
    			Nullable.nullable(resultSet.getBinaryStream(columnName)).mapThrower(inputStream -> {
    				try {
    					return IOs.toByteArrayInputStream(inputStream);
    				} catch (IOException e) {
    					throw new SQLException(e);
    				}
    			}).get(), InputStream.class), BINARYSTREAM_WRITER);

    	/**
    	 * Equivalent to {@link DefaultParameterBinders#BYTES_BINDER} but makes a copy of the read binary stream from ResultSet, else later reading fails
    	 * with "Stream or LOB value cannot be retrieved more than once".
    	 */
    	public static final ParameterBinder<byte[]> BYTES_BINDER = new LambdaParameterBinder<>(new LambdaResultSetReader<>((ResultSet resultSet, String columnName) ->
    			Nullable.nullable(resultSet.getBytes(columnName)).mapThrower(bytes -> Arrays.copyOf(bytes, bytes.length)).get(), byte[].class), BYTES_WRITER);

    	/**
    	 * Equivalent to {@link DefaultParameterBinders#BLOB_BINDER} but makes a copy of the read binary stream from ResultSet, else later reading fails
    	 * with "Stream or LOB value cannot be retrieved more than once".
    	 */
    	public static final ParameterBinder<Blob> BLOB_BINDER = new LambdaParameterBinder<>(new LambdaResultSetReader<>((ResultSet resultSet, String columnName) ->
    			Nullable.nullable(resultSet.getBlob(columnName)).mapThrower(blob -> new InMemoryBlobSupport(blob.getBytes(1, (int) blob.length()))).get(), Blob.class), BLOB_WRITER);

    	/**
    	 * Dedicated {@link LocalDateTime} {@link ParameterBinder} for Derby because its TIMESTAMP SQL type stores all 9 nanosecond digits (at least
    	 * in 10.5, wasn't the case in some previous versions), therefore it defers from other databases and SQL-92 standard that stores 6 digits by
    	 * default. Furthermore, Derby TIMESTAMP can't be configured for precision. So, to keep a homogeneous behavior between databases it was choosen
    	 * to store only 6 firsts digits of given {@link LocalDateTime} nanos : this binder does it.
    	 */
    	public static final ParameterBinder<LocalDateTime> LOCALDATETIME_BINDER = new NullAwareParameterBinder<>(
    			new NullAwareResultSetReader<>(DefaultParameterBinders.LOCALDATETIME_BINDER),
    			new NullAwarePreparedStatementWriter<>((preparedStatement, valueIndex, localDateTime) -> {
    				localDateTime = localDateTime.minusNanos(localDateTime.getNano() % 1000);	// 3 last digits are erased (input is not changed)
    				Timestamp timestamp = Timestamp.valueOf(localDateTime);
    				preparedStatement.setTimestamp(valueIndex, timestamp);
    			}));

    	/**
    	 * Dedicated {@link LocalTime} {@link ParameterBinder} for Derby because its TIMESTAMP SQL type stores all 9 nanosecond digits (at least
    	 * in 10.5, wasn't the case in some previous versions), therefore it defers from other databases and SQL-92 standard that stores 6 digits by
    	 * default. Furthermore, Derby TIMESTAMP can't be configured for precision. So, to keep a homogeneous behavior between databases it was choosen
    	 * to store only 6 firsts digits of given {@link LocalDateTime} nanos : this binder does it.
    	 */
    	public static final ParameterBinder<LocalTime> LOCALTIME_BINDER = new NullAwareParameterBinder<>(
    			new NullAwareResultSetReader<>(DefaultParameterBinders.LOCALTIME_BINDER),
    			new NullAwarePreparedStatementWriter<>((preparedStatement, valueIndex, localDateTime) -> {
    				localDateTime = localDateTime.minusNanos(localDateTime.getNano() % 1000);	// 3 last digits are erased (input is not changed)
    				Timestamp timestamp = Timestamp.valueOf(localDateTime.atDate(LocalTimeBinder.DEFAULT_TIMESTAMP_REFERENCE_DATE));
    				preparedStatement.setTimestamp(valueIndex, timestamp);
    			}));

    	private DerbyParameterBinders() {
    	}
    }